Rust Macro

发布于 2017-12-24 23:41:36

其他语言宏替换

C 语言宏替换

基本用法

  • 标识符别名
  • 宏函数

特殊用法

  • 单个#号:字符串化,用双引号包含
  • 两个###:参数名称直接被放置到参数位置

缺点

  • 不做语法检查
  • 会改变求值顺序
  • 宏参数的重复调用导致非预期行为
  • 不会递归展开
  • 宏参数的不完全展开(宏体存在##的时候)(否则,宏参数本身如果是宏,会在代入之前也做展开)

ANSI C标准中有几个标准预定义宏:

__LINE__:在源代码中插入当前源代码行号;
__FILE__:在源文件中插入当前源文件名;
__DATE__:在源文件中插入当前的编译日期
__TIME__:在源文件中插入当前编译时间;
__STDC__:当要求程序严格遵循ANSI C标准时该标识被赋值为1;
__cplusplus:当编写C++程序时该标识符被定义。
_FUNCTION_  当前所在函数名

Rust 宏

语法解析过程

token --> token-tree --> AST --> 

token上下文

每个标识符都被赋予了一个看不见的“句法上下文”。在比较两个标识符时,只有在标识符的明面名字和句法上下文都一致的情况下,两个标识符才能被视作等同。

macro_rules!

macro_rules! $name {
    $rule0 ;
    $rule1 ;
    // …
    $ruleN ;
}

规则由如下形式构成(注意小括号和花括号)

($pattern) => {$expansion}

当一个宏被调用时,对应的 macro_rules 解释器将一一依序检查规则

匹配、捕获过程

匹配分为 token-tree 匹配和 捕获 匹配。他们可以混合使用。有如下所示的各种匹配:

ident: 标识符,用来表示函数或变量名
block: 代码块,用花括号包起来的多个语句
expr: 表达式
pat: 模式,普通模式匹配(非宏本身的模式)中的模式,例如 Some(t), (3, 'a', _)
path: 路径,注意这里不是操作系统中的文件路径,而是用双冒号分隔的限定名(qualified name),如 std::cmp::PartialOrd
tt: 单个语法树
ty: 类型,语义层面的类型,如 i32, char
item: 条目,比如函数、结构体、模组等。
meta: 元条目,即被包含在 #[...]及#![...]属性内的东西。
stmt: 单条语句,如 let a = 42;

item: anything.
block: anything.
stmt: => , ;
pat: => , = if in
expr: => , ;
ty: , => : = > ; as
ident: anything.
path: , => : = > ; as
meta: anything.
tt: anything.

匹配重复

$ ( ... ) sep rep
// 括号内是重复的模式
// sep 是分隔符
// rep 表示重复次数 允许的值为 + 或 *

一般而言,在书写宏规则时,应从最具体的开始写起,依次写至最不具体的。

非标识符的标识符

  • self作为rust语言里的语言关键词,在宏匹配的时候,可以被看做标识符(identifier)
  • _ 是rust语言里的关键词,却不能作为宏里的标识符

调试宏

  • trace_macros!: trace_macros(true)
  • 编译器rustc-Z trace-macros参数
  • log_syntax! 输出所有传递给它的标识符树(通过在文件头添加#![feature(log_syntax)]启用)
  • 编译器rustc--pretty参数

宏的作用域

  • 类似于rust代码的词法作用域;同一文件里,定义了宏后面的地方才可以使用
  • #[macro_use]导入它所修饰的模块里导出的宏
    • 修饰extern crate导出的宏具有全局位置提升的效果,整个模块可用
  • 子模块可以覆盖父模块里可见的同名宏
  • 建议在库的最顶层导入所有用到的宏

导入导出

  • #[macro_export]导出它所修饰的宏定义
  • #[macro_use(X)]仅导入宏X

References